﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Algorithms.Expressions;
using System.Diagnostics;

namespace Algorithms.SetResolver
{
    public class SetResolverTarget : Algorithms.IGeneticAlgorithmTarget
    {
        #region Fields

        private InputSetEntry[] _set;
        private int _genomeLength;

        #endregion // Fields

        #region Properties

        public InputSetEntry[] Set { get { return _set; } }

        public int GenomeLength { get { return _genomeLength; } }

        public bool SupportRelativeFunctions { get; set; }

        public ExpressionFactoryContainer ExpressionFactoryContainer { get; set; }

        public IExpressionTreeEvaluator ExpressionTreeEvaluator { get; set; }

        #endregion // Properties

        #region Constructor

        public SetResolverTarget(InputSetEntry[] set,
            int genomeLength)
        {
            if (set == null)
                throw new ArgumentNullException("set");

            // genomeLength can't be even since we always need to end with an operation (nno)
            if (genomeLength % 2 == 0)
                throw new ArgumentException("The length of a genome can't be even", "genomeLength");

            if (genomeLength < 1)
                throw new ArgumentException("The length of a genome can't be less then 1", "genomeLength");

            _set = set;
            _genomeLength = genomeLength;

            ExpressionFactoryContainer = new ExpressionFactoryContainer();
            ExpressionFactoryContainer.AddExpressionFactories(DefaultExpressionFactoryLoader.Load());

            //ExpressionTreeEvaluator = new LambdaExpressionTreeEvaluator();
            ExpressionTreeEvaluator = new ManualExpressionTreeEvaluator();
        }

        #endregion // Constructor

        #region Methods

        public double CalculateFitness(int[] genes)
        {
            double totalFitness = 0;
            int totalFitnessContributions = 0;
            double[] calculatedValues = new double[Set.Length];

            // Build an expression tree in order to calculate the resulting value
            Expression expressionTree = BuildExpressionTree(genes);

            // first, resolve the expression for each entry and store the calculated value
            for (int entry = 0; entry < Set.Length; entry++)
            {
                int index = Set[entry].Index;

                double calculatedValue = ExpressionTreeEvaluator.Evaluate(expressionTree, index);

                calculatedValues[entry] = calculatedValue;
            }

            // next, calculate the fitness of each entry by comparing the distance between each value with the distance from each other value in relation to the same distances from the input set
            for (int entry = 0; entry < Set.Length; entry++)
            {
                double entryValue = Set[entry].Value;
                double calculatedValue = calculatedValues[entry];

                for (int otherEntry = 0; otherEntry < Set.Length; otherEntry++)
                {
                    if (entry != otherEntry)
                    {
                        // There is no logic to calculate this for ourselfs
                        double otherEntryValue = Set[otherEntry].Value;
                        double otherCalculatedValue = calculatedValues[otherEntry];

                        double entryDistance = entryValue / otherEntryValue;
                        double calculatedDistance = calculatedValue / otherCalculatedValue;

                        if (entryDistance < 0)
                            entryDistance = -entryDistance;
                        if (calculatedDistance < 0)
                            calculatedDistance = -calculatedDistance;

                        double fitness;

                        if (entryDistance > calculatedDistance)
                        {
                            fitness = (1 / entryDistance) * calculatedDistance;
                        }
                        else if (entryDistance < calculatedDistance)
                        {
                            fitness = (1 / calculatedDistance) * entryDistance;
                        }
                        else
                        {
                            if (double.IsNaN(calculatedDistance))
                            {
                                fitness = 0;
                            }
                            else
                            {
                                // equal so fitness is 1
                                fitness = 1;
                            }
                        }

                        // we can't deal with either non-numbers or infinite numbers so declare this as no fitness
                        if (double.IsNaN(fitness) || double.IsInfinity(fitness))
                        {
                            fitness = 0;
                        }

                        totalFitnessContributions++;
                        totalFitness += fitness;
                    }
                }
            }


            if (totalFitnessContributions > 0)
            {
                totalFitness = totalFitness / totalFitnessContributions;
            }

            Debug.Assert(totalFitness >= 0 && totalFitness <= 1);

            if (totalFitness == 1)
            {
                if (!SupportRelativeFunctions)
                {
                    // We might have found a matching function but need to make sure that the numbers are also equal absolutely, else, we are very close so set a high fitness < 1
                    for (int index = 0; index < Set.Length; index++)
                    {
                        double entryValue = Set[index].Value;
                        double calculatedValue = calculatedValues[index];

                        if (entryValue != calculatedValue)
                        {
                            totalFitness = 0.9;
                            break;
                        }
                    }
                }
            }

            return totalFitness;
        }

        public Expression BuildExpressionTree(int[] genes)
        {
            Stack<Expression> operandStack = new Stack<Expression>(genes.Length - 1);

            // folowing: http://stackoverflow.com/questions/3947937/algorithm-for-permutations-of-operators-and-operands/3948113#3948113
            for (int index = 0; index < genes.Length; index++)
            {
                int gene = genes[index];
                int genesLeft = genes.Length - index;

                IExpressionFactory expressionFactory = ExpressionFactoryContainer.FindExpressionFactory(gene, operandStack.Count, genesLeft);
                Expression[] arguments = new Expression[expressionFactory.RequiredArgumentLength];

                for (int argumentIndex = 0; argumentIndex < expressionFactory.RequiredArgumentLength; argumentIndex++)
                {
                    Expression argument = operandStack.Pop();
                    arguments[argumentIndex] = argument;
                }

                Expression expression = expressionFactory.Create(arguments);

                operandStack.Push(expression);
            }

            Debug.Assert(operandStack.Count == 1);

            // should be 1 operand left on the stack which is the ending result
            Expression rootExpression = operandStack.Pop();

            // if the whole construction failed, create a constant expression returning 0
            if (rootExpression is DefaultExpression)
            {
                rootExpression = ConstantExpression.Constant(0d, typeof(double));
            }
            return rootExpression;
        }

        #endregion // Methods

        #region Helpers

        #endregion // Helpers
    }
}
